Technote FL 33 | October 1985 |
SFPGetFile's dialog hook function and file filter function enable you to customize SFPGetFile's behavior to fit the needs of your application. This technical note consists primarily of a short example program that
1) changes the title of the Open button to `MyOpen',
2) adds two radio buttons so that the user can choose to display either text files or text files and applications.
3) adds a quit button to the SFPGetFile dialog,
All this is done in a way so as to provide compatibility with the Macintosh File System (MFS), the Hierarchical File System (HFS) and (hopefully) future systems. If you have any questions as you read, the complete source of the demo program and the resource compiler input file is provided at the end of this technical note.
Basically, we need to do three things: add our extra controls to the resource
compiler input file, write a dialog hook function, and write a file filter
function.
First we need to define a dialog in our resource file. It will be DLOG #128:
CONST myDLOGID = 128;
and it's Rez description is:
resource 'DLOG' (128, purgeable) { {0, 0, 200, 349}, dBoxProc, invisible, noGoAway, 0x0, 128, "MyGF" };
The above coordinates (0 0 200 349) are from the standard Standard File dialog. If you need to change the size of the dialog to accommodate new controls, change these coordinates. Next we need to add a DITL in our resource file that is the same as the standard HFS DITL #-4000 except for one item. We need to change the left coordinate of UserItem #4, or part of the dialog will be hidden if we're running under MFS:
/* [4] */ /* left coordinate changed from 232 to 252 so program will work on MFS */ {39, 252, 59, 347}, UserItem { disabled };
None of the other items of the DITL should be changed, so that your program will remain as compatible as possible with different versions of Standard File. Finally, we need to add three items to this DITL, two radio buttons and one button (to serve as a quit button)
/* [11] textButton */ {1, 14, 20, 142}, RadioButton { enabled, "Text files only" }; /* [12] textAppButton */ {19, 14, 38, 176}, RadioButton { enabled, "Text and applications" }; /* [13] quitButton */ {6, 256, 24, 336}, Button { enabled, "Quit" }
Because we've added three items, we need also need to change the item count for the DITL from 10 to 13. We also include the following in our resource file:
resource 'STR#' (256) { {/* array StringArray: 1 elements */ /* [1] */ "MyOpen" } };
That's all there is to modify in the resource file.
We will be calling SFPGetFile as follows:
SFPGetFile (wher, '', @SFFileFilter, NumFileTypes, MyFileTypes, @MySFHook, reply, myDLOGID,nil);
Notice that we're passing @MySFHook to Standard File. This is the address of our dialog hook routine. Our dialog hook is declared as:
FUNCTION MySFHook(MySFitem: INTEGER; theDialog: DialogPtr):INTEGER;
A dialog hook routine allows us to see every item hit before standard file acts on it. This allows us to handle controls that aren't in the standard SFPGetFile's DITL or to handle standard controls in non-standard ways. The dialog hook in this example consists of a case statement with MySFitem as the case selector. Before SFPGetFile displays its dialog, it calls our dialog hook, passing it a -1 as MySFitem. This gives us a chance to initialize our controls. Here we will set the textAppButton to off and the textButton to on:
GetDItem(theDialog,textAppButton,itemType,itemToChange,itemBox); SetCtlValue(controlHandle(itemToChange),btnOff); GetDItem(theDialog,textButton,itemType,itemToChange,itemBox); SetCtlValue(controlHandle(itemToChange),btnOn);
and we can also change the title of an existing control. Here's how we might change the title of the Open button using a string that we get from a resource file:
GetIndString(buttonTitle,256,1); If buttonTitle <> '' then Begin { if we really got the resource} GetDItem(theDialog,getOpen,itemType,itemToChange,itemBox); SetCtitle(controlHandle(itemToChange),buttonTitle); End; {if} {if we didn't get the resource, don't change the title}
Upon completion of our routine that handles the -1, we return a -1 to standard file:
MySFHook:= MySFItem; {pass back the same item we were sent}
We now have a SFPGetFile dialog displayed that has a quit button and two radio buttons (the textOnly button is on, the TextApp button is off). In addition, the standard Open button has been renamed to MyOpen (or whatever STR is the first string in STR# 256). This was all done before SFPGetFile displayed the dialog. Once our hook is exited, SFPGetFile displays the dialog and calls ModalDialog.
When the user clicks on an item in the dialog, our hook is called again. We can then take appropriate actions, such as highlighting the textButton and un-highlighting the textAppButton if the user clicks on the textButton. At this time, we can also update a global variable (textOnly) that we will use in our file filter function to tell us which files to display. Notice that we can redisplay the file list by returning a 101 as the result of MySFHook. (Standard File for Systems newer than 4.3 will also read the low memory globals, CurDirStore and SFSaveDisk, and switch directories when necessary if a 101 is returned as the result. Thus, you can point Standard File to a new directory, or a new disk.) For example, when the textButton is hit we turn the textAppButton off, turn the textButton on, update the global variable textOnly, and tell SFPGetFile to redisplay the list of files the user can choose from:
if not textOnly then Begin {if textOnly was turned off, turn it on now} GetDItem(theDialog,textAppButton,itemType,itemToChange,itemBox); SetCtlValue(controlHandle(itemToChange),btnOff); GetDItem(theDialog,textButton,itemType,itemToChange,itemBox); SetCtlValue(controlHandle(itemToChange),btnOn); textOnly:=TRUE; {toggle our global variable for use in the filter} MySFHook:= reDrawList;{101} {we must tell SF to redraw the list} End; {if not textOnly}
If our quit button is hit, we can pass SFPGetFile back the cancel button:
MySFHook:= getCancel;
If one of SFPGetFile's standard items is hit, it is very important to pass that item back to SFPGetFile:
MySFHook:= MySFItem; {pass back the same item we were sent}
Remember, we called SFPGetFile as follows:
SFPGetFile (wher, '', @SFFileFilter, NumFileTypes, MyFileTypes, @MySFHook, reply,myDLOGID,nil);
Notice that we're passing @SFFileFilter to SFPGetFile. This is the address of our file filter routine. A file filter is declared as:
FUNCTION SFFileFilter (p: ParmBlkPtr): BOOLEAN;
A file filter routine allows us to control which files SFPGetFile will display for the user. Our file filter is called for every file (of the type(s) specified in the typelist) on an MFS disk, or for every file (of the type(s) specified in the typelist) in the current directory on an HFS disk. In addition, SFPGetFile displays HFS folders for us automatically. Our file filter selects which files should appear in the dialog by returning FALSE for every file that should be shown and TRUE for every file that shouldn't.
For example, using our global variable textOnly (which we set in our dialog hook, remember?):
FUNCTION SFFileFilter(p:parmBlkPtr):boolean; Begin {SFFileFilter} SFFileFilter:= TRUE; {Don't show it -- default} if textOnly then if p^.ioFlFndrInfo.fdType = 'TEXT' then SFFileFilter:= FALSE {Show TEXT files only} else Begin End {dummy else} else if (p^.ioFlFndrInfo.fdType = 'TEXT') or (p^.ioFlFndrInfo.fdType = 'APPL') then SFFileFilter:= FALSE; { show TEXT or APPL files} End; {SFFileFilter}
SFPGetFile calls the file filter after it has called our dialog hook. Please remember that the filter is passed every file of the types specified in the typelist (MyFileTypes). If you want your application to be able to choose from all files, pass SFPGetFile a -1 as numTypes. For information about parameters to SFPGetFile that haven't been discussed in this technical note, see the Standard File Package chapter of Inside Macintosh.
That's all there is to it!! Now that you know how to modify SFPGetFile to suit your needs, please don't rush off and load up the dialog window with all kinds of controls and text. Please make sure that you adhere to Macintosh interface standards. Similar techniques can be used with SFGetFile, SFPutFile and SFPPutFile.
The complete source of the demo program and of the resource compiler input file follows:
{$R-} {Jim Friedlander Macintosh Technical Support 9/30/85} program SFGetDemo; USES MemTypes, QuickDraw, OSIntf, ToolIntf, PackIntf; {$D+} CONST myDLOGID = 128; {ID of our dialog for use with SFPGetFile} VAR wher: Point; { where to display dialog } reply: SFReply; { reply record } textOnly: BOOLEAN; { tells us which files are currently being displayed} myFileTypes: SFTypeList; { we won't actually use this } NumFileTypes: integer; {----------------------------------------------------------------------------} FUNCTION MySFHook(MySFitem:integer; theDialog:DialogPtr): integer; CONST textButton = 11; {DITL item number of textButton} textAppButton = 12; {DITL item number of textAppButton} quitButton = 13; {DITL item number of quitButton} stayInSF = 0; {if we want to stay in SF after getting an Open hit, we can pass back a 0 from our hook (not used inthis example) } firstTime = -1; {the first time our hook is called, it is passed a -1} {The following line is the key to the whole routine -- the magic 101!!} reDrawList = 101; {returning 101 as item number will cause the file list to be recalculated} btnOn = 1; {control value for on} btnOff = 0; {control value for off} VAR itemToChange: Handle; {needed for GetDItem and SetCtlValue} itemBox:Rect; {needed for GetDItem} itemType:integer; {needed for GetDItem} buttonTitle: Str255; {needed for GetIndString} Begin {MySFHook} case MySFItem of firstTime: Begin { before the dialog is drawn, our hook gets called with a -1 (firstTime) as the item so we can change things like button titles, etc. } {Here we will set the textAppButton to OFF, the textButton to ON} GetDItem(theDialog,textAppButton,itemType,itemToChange,itemBox); SetCtlValue(controlHandle(itemToChange),btnOff); GetDItem(theDialog,textButton,itemType,itemToChange,itemBox); SetCtlValue(controlHandle(itemToChange),btnOn); GetIndString(buttonTitle,256,1); {get the button title from a resource file} If buttonTitle <> '' then Begin { if we got the resource} GetDItem(theDialog,getOpen,itemType,itemToChange,itemBox); {get handle to open button} SetCtitle(controlHandle(itemToChange),buttonTitle); End; {if} {if we can't get the resource, we just won't change the open button's title} MySFHook:= MySFItem; {pass back the same item we were sent} End; {firstTime} {Here we will turn the textAppButton OFF, the textButton ON and redraw the list} textButton: Begin if not textOnly then Begin GetDItem(theDialog,textAppButton,itemType,itemToChange,itemBox); SetCtlValue(controlHandle(itemToChange),btnOff); GetDItem(theDialog,textButton,itemType,itemToChange,itemBox); SetCtlValue(controlHandle(itemToChange),btnOn); textOnly:=TRUE; MySFHook:= reDrawList; {we must tell SF to redraw the list} End; {if not textOnly} End; {textOnlyButton} {Here we turn the textButton OFF, the textAppButton ON and redraw the list} textAppButton: Begin if textOnly then Begin GetDItem(theDialog,TextButton,itemType,itemToChange,itemBox); SetCtlValue(controlHandle(itemToChange),BtnOff); GetDItem(theDialog,TextAppButton,itemType,itemToChange,itemBox); SetCtlValue(controlHandle(itemToChange),BtnOn); TextOnly:=FALSE; MySFHook:= reDrawList; {we must tell SF to redraw the list} End; {if not textOnly} End; {textAppButton} quitButton: MySFHook:= getCancel; {Pass SF back a 'cancel button'} {!!!!very important !!!! We pass SF's 'standard' hits back to SF} otherwise Begin MySFHook:= MySFItem; { the item hit was one of SF's standard items... } End; {otherwise} { so just pass it back} End; {case} End; {MySFHook} {-----------------------------------------------------------------------------} FUNCTION SFFileFilter(p:parmBlkPtr):boolean; {general strategy -- check value of global vartextOnly to see which files to display} Begin {SFFileFilter} SFFileFilter:= TRUE; {Don't show it -- default} if textOnly then if p^.ioFlFndrInfo.fdType = 'TEXT' then SFFileFilter:= FALSE {Show it} else Begin End {dummy else} else if (p^.ioFlFndrInfo.fdType = 'TEXT') or (p^.ioFlFndrInfo.fdType = 'APPL') then SFFileFilter:= FALSE; {Show it} End; {SFFileFilter} {-----------------------------------------------------------------------------} Begin {main program} InitGraf (@thePort); InitFonts; InitWindows; TEInit; InitDialogs (nil); wher.h:=80; wher.v:=90; NumFileTypes:= -1; {Display all files} { we don't need to initialize MyFileTypes, because we want to get a chance to filter every file on the disk in SFFileFilter - we will decide what to show and what not to. If you want to filter just certain types of files by name, you would set up MyFileTypes and NumFileTypes accordingly} repeat {each time SFPGetFile is called, display will be text-only files} textOnly:= TRUE; SFPGetFile (wher, '', @SFFileFilter, NumFileTypes, MyFileTypes, @MySFHook,reply,myDLOGID,nil); until reply.good = FALSE; {until a cancel button hit ( or a Quit button -- thanks to our dialog hook ) } End.
#include <Types.h>#include <Quickdraw.h>#include <Resources.h>#include <Fonts.h>#include <Windows.h>#include <Menus.h>#include <TextEdit.h>#include <Events.h>#include <Dialogs.h>#include <Packages.h>#include <Files.h>#include <Controls.h>#include <ToolUtils.h>#define textButton 11 /*DITL item number of textButton*/ #define textAppButton 12 /*DITL item number of textAppButton*/ #define quitButton 13 /*DITL item number of quitButton*/ #define stayInSF 0 /*if we want to stay in SF after getting an Open hit, we can pass back a 0 from our hook (not used in this example) */ #define firstTime -1 /* the first time our hook is called, it is passed a -1*/ #define reDrawList 101 /* This line is the key to the whole routine the magic 101!! returning 101 as item number will cause the file list to be recalculated*/ #define btnOn 1 /*control value for on*/ #define btnOff 0 /*control value for off*/ #define myDLOGID 128 /*resource ID of our DLOG for SFPGetFile*/ Boolean textOnly; /* tells us which files are currently being displayed*/ main() { /*main program*/ pascal short MySFHook(); pascal Boolean flFilter(); Point wher; /* where to display dialog*/ SFReply reply; /* reply record */ SFTypeList myFileTypes; /* we won't use this */ short int NumFileTypes = -1; InitGraf(&qd.thePort); InitFonts(); FlushEvents(everyEvent, 0); InitWindows(); TEInit(); InitDialogs(nil); InitCursor(); wher.h=80; wher.v=90; /* we don't need to initialize MyFileTypes, because we want to get a chance to filter every file on the disk in flFilter - we will decide what to show and what not to. if you want to filter just certain types of files by name, you would set up MyFileTypes and NumFileTypes accordingly*/ do { textOnly= true; /*each time SFPGetFile is called, initial display will be text-only files*/ SFPGetFile(&wher,"",flFilter, NumFileTypes,myFileTypes, MySFHook, &reply,myDLOGID,nil); } while (reply.good); /*until we get a cancel button hit (or a Quit button in this case ) */ } /* main */ pascal short MySFHook(short MySFItem,DialogPtr theDialog) { Handle itemToChange; /*needed for GetDItem and SetCtlValue*/ Rect itemBox; /*needed for GetDItem*/ short itemType; /*needed for GetDItem*/ char buttonTitle[256]; /*needed for GetIndString*/ switch (MySFItem) { case firstTime: /* before the dialog is drawn, our hook gets called with a -1 (firstTime)... as the item so we can change things like button titles, etc. Here we set the textAppButton to OFF, the textButton to ON*/ GetDItem(theDialog,textAppButton,&itemType,&itemToChange,&itemBox); SetCtlValue(itemToChange,btnOff); GetDItem(theDialog,textButton,&itemType,&itemToChange,&itemBox); SetCtlValue(itemToChange,btnOn); /*get the button title from a resource file*/ GetIndString((char *)buttonTitle,256,1); if (buttonTitle[0] != 0) /* check the length of the p-string to see if we got the resource*/ { /*get a handle to the open button*/ GetDItem(theDialog,getOpen,&itemType,&itemToChange,&itemBox); SetCTitle(itemToChange,buttonTitle); } /*if we can't get the resource, we just won't change the open button's title*/ return MySFItem; /*pass back the same item we were sent*/ break; /*Here we turn the textAppButton OFF, the textButton ON and redraw the list*/ case textButton: if (!textOnly) { GetDItem(theDialog,textAppButton,&itemType,&itemToChange,&itemBox); SetCtlValue(itemToChange,btnOff); GetDItem(theDialog,textButton,&itemType,&itemToChange,&itemBox); SetCtlValue(itemToChange,btnOn); textOnly=true; return(reDrawList); /*must tell SF to redraw the list*/ } /*if !textOnly*/ return MySFItem; break; /*Here we will turn the textButton OFF, the textAppButton ON and redraw the list*/ case textAppButton: if (textOnly) { GetDItem(theDialog,textButton,&itemType,&itemToChange,&itemBox); SetCtlValue(itemToChange,btnOff); GetDItem(theDialog,textAppButton,&itemType,&itemToChange,&itemBox); SetCtlValue(itemToChange,btnOn); textOnly=false; return(reDrawList); /* must tell SF to redraw the list*/ } /*if not textOnly*/ return MySFItem; /*pass back the same item we were sent*/ break; case quitButton: return(getCancel); /*Pass SF back a 'cancel button'*/ /*!!!very important !!!! We must pass SF's 'standard' item hits back to SF*/ default: return(MySFItem); /* the item was one of SF's standard items... */ } /*switch*/ return(MySFItem); /* return what we got */ } /*MySFHook*/ pascal Boolean flFilter(pb) FileParam *pb; { /* is this gross or what??? */ return((textOnly) ? ((pb->ioFlFndrInfo.fdType) != 'TEXT') : ((pb->ioFlFndrInfo.fdType) != 'TEXT') && ((pb->ioFlFndrInfo.fdType) != 'APPL')); } /*flFilter*/
#include "types.r" resource 'STR#' (256) { { "MyOpen" } }; resource 'DLOG' (128, purgeable) { {0, 0, 200, 349}, dBoxProc, invisible, noGoAway, 0x0, 128, "MyGF" }; resource 'DITL' (128, purgeable) { { /* [1] */ {138, 256, 156, 336}, Button { enabled, "Open" }; /* [2] */ {1152, 59, 1232, 77}, Button { enabled, "Hidden" }; /* [3] */ {163, 256, 181, 336}, Button { enabled, "Cancel" }; /* [4] */ {39, 252, 59, 347}, UserItem { disabled }; /* [5] */ {68, 256, 86, 336}, Button { enabled, "Eject" }; /* [6] */ {93, 256, 111, 336}, Button { enabled, "Drive" }; /* [7] */ {39, 12, 185, 230}, UserItem { enabled }; /* [8] */ {39, 229, 185, 245}, UserItem { enabled }; /* [9] */ {124, 252, 125, 340}, UserItem { disabled }; /* [10] */ {1044, 20, 1145, 116}, StaticText { disabled, "" }; /* [11] */ {1, 14, 20, 142}, RadioButton { enabled, "Text files only" }; /* [12] */ {19, 14, 38, 176}, RadioButton { enabled, "Text and applications" }; /* [13] */ {6, 256, 24, 336}, Button { enabled, "Quit" } } };